Welcome back.
In this lecture we're going to script the server script that's going to validate and handle the guns
in our game.
The purpose of this server script is to listen to that gun remote event and do server side effects for
our guns, like the muzzle flash and dealing damage.
So to get started, we'll create a new script in Server Script service and I'm going to call this my
guns handler.
We'll paste in that same template that we've been using.
I'll get rid of this section and then this section, and we can go ahead and grab replicated storage
to get access to that event.
And we're also going to grab the player service.
And then we can go ahead and create a couple variables.
One for our gun comms enum.
So we'll require for replicated storage and modules dot enums.
We'll get our gun comms enum.
And then we can go ahead and get our gun event.
And that's replicated storage dot remotes dot gun event.
Then we can go ahead and listen to this event.
So gun event dot on server event we're going to connect.
Get a function and will be passed the player.
They will pass us the action we would like the server to perform as well as any additional arguments.
We'll call args.
Now, since this event is going to be fired when we want to either reload or shoot our gun, we want
to make sure we stop any exploiters from messing with our remote event.
So, for example, if the player that fired this event does not have a character model, then we're
just going to return because how are they shooting or reloading a gun when they don't even have a character
model?
The next thing we need to verify is that this player is actually holding a gun in their hand.
So make a variable, we'll call it gun, and it's going to be equal to our player's character.
And we're going to find first child on them which is a tool.
If we do not find such a tool.
So if not gun.
Or if they do have a toe on them, but it's not a gun.
So if not gun, get attribute gun type, if it does not have the gun type attribute, then we're just
going to return because they're not holding a tool or they don't have a gun on them.
Otherwise, if they do have a gun on them, then we can go ahead and grab the properties for that gun
and we'll require the gun dot properties.
Then we can go ahead and check the action they want us to do.
So if the action is equal to the gun comms enum dot two server for example validate shot.
Then we'll perform the functionality for that.
Otherwise if the action is equal to the gun comms enum dot two server dot validate reload, then we'll
perform an action for that.
And we can go ahead and actually create two functions.
For doing that we'll have a function called reload.
And then we can also have a function called shoot.
And to these functions, we could pass the player the gun.
They're holding the properties for their gun.
Copy that and do it for both.
And then specifically for our shoot function, we're also going to want to get the arguments along with
the gun and its properties.
So for example if they hit anything what are the instances they hit.
What position did they hit him at stuff like that.
And then when we validate a shot, what we want to make sure of is that the arguments we're being passed
are correct.
So if an exploiter decides to tamper with our event and try to force the server to error or cause some
kind of problem, we want to prevent that from happening by checking to see what this argument is,
so we can get the type of the argument we'll call it.
What type?
And that's going to be equal to the type of our argument.
If our type, let's say it's not equal to a table, which is what we expect.
And our type is not equal to nothing.
Then we need to just return and give out a warning saying something like incorrect data passed from
the client.
So client to server, because we expect that this args parameter is either going to be a table or nothing.
So if the player hits something then this will be a table full of information.
But if they don't hit anything then it'll be nil.
But if it's neither a table nor a nil, then that means an exploiter is messing with this remote event.
So we're just going to return.
Another thing we want to check is the contents inside of our args table.
So what we want to do is we want to check if the gun.
Get attribute gun type is equal to standard because the standard gun or the shotgun is going to send
us different information inside of this argument table.
So if the attribute of the gun type is standard and let's say what type is equal to table, then what
we need to check is we need to check if the type of inside of the arguments table, the raycast result
key.
If this is not also equal to a table.
So if we have a problem there, or another problem we could have is the type of the args.
Instance position that we also pass.
If that is not equal to the type of vector three, then we've also encountered another issue.
So in this instance we also want to return because yes, they sent us a table.
But the stuff in the table does not match what we'd expect.
So we need to return and then give another warning.
And we can basically just give the exact same message.
And then we could also segregate this into different ones.
So this is the first warning.
This is our second warning.
Otherwise once we have verified that the information coming to the server is correct, then we can use
our shoot function and we'll pass the player that fired this event and we'll pass their gun, the properties
for the gun and the arguments.
Now for reloading.
We don't need to verify or validate any information here, because the client is not going to be sending
us any information except for the action they would like us to perform.
So we could just directly call our reload function, pass our player the gun they're holding and the
properties for that gun.
So let's go ahead and fill out our shoot function.
The first thing we want to verify is if there's actually ammo in the magazine of the gun.
So if the gun get attribute ammo and mag if that is equal to zero, then we're just going to return
and not do anything because they don't have any ammo.
Another thing we want to verify is we want to keep track here on the server.
When's the last time the player fired this event to shoot their gun?
And that's because an exploiter could easily bypass the cooldown that we set on the client, and rapid
fire this remote event to shoot their gun super, super fast.
So we also need to keep track of when the player last shot their gun here on the server as well.
So I'll create a table and I'm going to call this table time since last shot.
And we're going to store the players in here.
And we're going to store each of their guns or whatever gun they use to fire this event with and check
to see when's the last time they fired a shot.
Of course, if this is the player's first time shooting their gun, they're not going to have anything
inside of this table, so we need to check for that.
So if they do not have a key inside of this table, then we'll set one up for them.
So time since last shot player dot name.
We'll set that equal to a table.
And this table is going to contain the names of the different guns they have.
Because a player could have multiple different guns.
And we'll be just using the gun's name as the identifier.
And since this is their first time firing the event by default, we'll set the time value equal to zero.
Another thing we need to check is if this is a different gun they're firing and they don't have that
gun inside of the table that's stored right here.
So if they do not have inside of time last shot for their table, if they do not have the gun's name
as a key inside of there, then we also need to set that up for them.
So we could just copy this and set that equal to zero as well.
Then we need to check if the current tick.
Subtracted by.
Whatever.
They last fired this particular gun.
If this is less than and this is where we're going to have to divide 60 by the property's fire rate.
So if they somehow fired this gun faster than what's possible for their fire rate, then we're just
going to return as well.
However, there is a little bit of a delay from the information being sent from the client to the server.
So just to account for any possible delays and just be a little bit generous to our players, we'll
subtract this value by 0.05 just to give them a little bit of extra wiggle room in case any lag occurs.
Otherwise, if everything checks out, then we can go ahead and update for this particular gun and set
the time they last shot this gun equal to the current tick.
And then we also want to make sure to deduct one bullet out of the ammo in their magazine.
So we could do gun set attribute ammo in mag.
And we'll get their current ammo.
So gun get attribute.
Ammo and a mag.
And subtract that by one.
And now here is where the server is going to have to check if this gun is a shotgun or if it's a standard
gun, because if it's a shotgun, we need to check every single pellet inside of the arguments that
are being passed to us.
Whereas if it's a normal gun, we only have to check a single bullet.
So if the gun get attribute gun type.
If that's equal to spread.
So it's a shotgun.
Then we need to verify some other information.
For example, if the number of elements or bullets inside of our args table is greater than the amount
of pellets for this gun, then this could indicate that an exploiter is trying to mess with our game.
So again, we're just going to return.
Otherwise, if everything checks out, then we can go ahead and create a muzzle flash on the gun.
We can play the gun sound, and then we can go and calculate the damage for each of the individual pellets.
So let's go ahead and create a couple functions.
One we can have to calculate the damage for a particular raycast result.
And then we could have another one to create a muzzle flash.
So we'll call it create muzzle flash.
And then we also want to have a function to create a sound.
So create.
Sound.
And actually what we can do for this function is just go back to our local script for our gun and copy
exactly what we've done here, and then paste that here in our server script as well.
We'll get past the parent and then the sound properties for that sound.
And then for our create muzzle flash function, we'll also get past a parent to create a new point light.
And then we'll also get past the properties for that point light.
So if you remember in each one of our guns, let me go and pick one out by random.
We defined muzzle flash properties for this gun such as the brightness, the color, the range and stuff
like that.
So we'll also want to get past the light properties for our point light.
So this function what we'll do here is we'll create a new light will be equal to instance dot new point
light.
And then for every single property.
And value inside of light properties.
What we want to do is we want to set that property on our light.
Then we can set the parent of our light equal to that parent, and then we'll make sure to enable the
light as well.
And then we'll return that light.
So that way we can destroy it after like 0.05 seconds or something.
So it's just a burst of light that appears in our game.
So now what we could do is when we shoot our gun.
We'll create a new muzzle flash, so we'll get the light from our create muzzle flash function.
The parent is going to be the gun dot handle and then the muzzle attachment.
And then we're going to pass the properties and it was called our muzzle flash properties.
Then I'm going to spawn a function and a new thread.
This function is just going to yield for a short period of time.
We could do something like 0.15 seconds.
You could also do task dot delay as well.
So task dot delay you could do 0.15.
It doesn't really matter.
Either way works.
So we'll do it with the delay method.
So after waiting 0.15 seconds then we'll just destroy the light.
And then we also want to emit a particle from the particle emitter that is inside of our gun.
So inside each one of the muzzle attachments for our guns there is a flash particle emitter.
So gun dot handle dot muzzle flash.
We're going to emit one particle.
And then we can go ahead and create the sound for the gun.
So create sound.
The parent is going to be our gun dot handle dot muzzle.
And then the properties is going to be our shoot sound properties.
Then we can play the sound.
And then we'll wait for the sound to end.
And when it does we're going to destroy it and clean it up.
And then finally after we do that for the gun, we can go ahead and loop through every single one of
those pellet results inside of our args table.
So if you remember back on the client when we shoot our gun and it's a shotgun, when we fire to the
server, we're passing a table containing all of these different information, such as the raycast result
and then the position of that instance that we hit.
So we're going to loop through every single result, if there is any, in our args table.
And this is where we could use our calculate damage function and pass all that information to it.
We could pass the properties of the gun.
We'll pass the results.
And then we'll make sure to pass the player as well to this function.
So calculate damage.
We will get passed the properties A results and then our player.
Now inside of this calculate damage function, we first want to verify that this result actually has
a value.
So if we do not have a result from the raycast, then we don't need to calculate any damage because
we didn't hit anything, so we'll return.
Otherwise what we can go ahead and do is we can check to see if we hit a humanoid or a character model
so we can get the character, and that's going to be equal to our result.
And remember we have a key in this table called raycast result and store it inside of there is our instance.
And then we can get the parent of our instance which should be a character model if we hit a character.
So if we do not find a humanoid in this character.
So if not character, find first child which is a humanoid.
Or if this character belongs to another player, then we don't want to damage them.
So.
Using the player service.
If we get player from character and pass our character variable and we actually get a player, then
we're just going to return.
So if we didn't hit a character or we did, but that belongs to a player, then we don't want to deal
any damage.
Otherwise, we can go ahead and calculate how much damage we need to do by grabbing the multiplier based
on what limb we hit, so we can get that multiplier from our properties.
And remember that's called our body part multipliers.
And we're just going to index this with the name of the instance that we hit.
So result dot raycast result dot instance.
And we'll get the name of that instance to grab that multiplier.
And then we can refer to that character, get its humanoid.
And then we'll use the take damage function to deal damage to it.
And that's going to be equal to our properties dot damage.
And then we need to multiply that by our multiplier.
And just in case, for some reason, if this variable right here is nil, we can put an Or here and
pass a default value of one.
So just in case, if we index our body part multipliers and we do not get a multiplier return, then
the default is going to be one.
And then if you remember with our knife when we attacked or hurt a zombie, it has that who last damaged
object value inside of it.
So we want to check if this character has fine first child who last damaged.
Which means whatever we are hurting is a zombie, so we can go ahead and update the character who last
damaged dot value and set that equal to the player.
Okay, cool.
So now we got this function set up and we can go ahead.
And now listen for every single pellet inside of our shotguns.
And then we want to check if our gun is in a shotgun.
Well we need to check if the gun get attribute gun type is equal to the standard gun type.
And if it is, then this makes our job a lot more easy.
We can basically just copy this code here, paste that there, and instead what we can do is just call
our calculate damage function without having to loop through everything.
And then we'll pass the properties.
We'll pass the args and we'll pass the player.
Okay.
So now let's actually go ahead and test whether or not we can shoot our gun and deal damage to humanoids.
So I'm going to create a rig here.
And I'll just duplicate this guy a couple times, and then we'll make sure to update the values of the
guns in our starter pack, make sure they actually have ammo in their magazines.
So I'll just put some ammo in there, and I'll put some ammo in this one.
And now we can go and playtest our game and see if the server is properly verifying the results coming
from our client.
So if I hold my gun out and let's, let's actually just test shooting.
So if I click.
We are firing to the server, but it does appear that the server is not doing anything while we're firing
this event.
We aren't getting any errors, but the server might be prematurely returning for some reason.
So let's go ahead and figure out why that is.
And I believe the issue I found it right away is we forgot to put a Not statement here.
So because we didn't put a not statement here, it's returning when the player is actually holding their
gun which we don't want to happen.
So we need to put a not statement here and return when the player is not holding a tool.
That is a gun.
Okay, now let's try and test this again and see if our guns are working.
So if I pull out my gun and I click perfect.
As you can see, the muzzle flash happened and it played the shoot sound.
So I'll click it again and it's shooting our gun.
And if we go and look in the workspace and we look at my player and we look at our gun, as you can
see it is properly deducting the value from the ammo and mag attribute.
So if I shoot again, as you can see it is deducting bullets from my ammo and magazine.
Let's go ahead and try to shoot a character.
So if I shoot them in their hand, as you can see, I have dealt very little damage.
But if I shoot them in the face, as you can see, I've dealt a lot of damage.
Very cool.
Let's try a shotgun.
Since there's going to be 12 pellets coming out of the shotgun.
This should do a lot of damage if I shoot them in the middle of their chest.
So if I click yep, just like that, they are dead.
And if I go and shoot this guy, let me actually back up a bit and have the spread.
Kind of be farther out.
And then I click.
There you go.
My rig did not die.
Let me back up a little bit more and click again and boom, they are dead.
Very cool.
And then if I keep shooting my guns, eventually I'll run out of ammo.
Now I have no more ammo.
And now what we need to implement is allowing the server to reload our guns when we press R on our keyboard.
So let's go ahead and implement that.
So inside of our reload function, what we want to go ahead and do is grab the current ammo inside of
the magazine for this player's gun.
So gun get attribute.
Ammo in magazine.
And then we also want to grab the ammo that they have in reserve.
So ammo reserve is going to be equal to gun get attribute ammo reserve.
Now, if the ammo in their magazine is equal to the properties of max mag ammo, then this player doesn't
need to reload, so we're just going to return.
If their ammo reserve is less than or equal to zero just in case, then they also can't reload because
they don't have any ammo to reload with.
Otherwise.
What we need to do is we need to verify that this player actually waited through their reload duration.
And a way we could check that is by actually listening to when they last shot their gun and comparing
that to the current tick, because during that entire time the player is reloading, they're not going
to be able to shoot that gun.
So by checking to see how long it's been since they last shot, we can check to see if that matches
up with the reload duration property we have set for the gun.
So what I'm going to do is I'm going to copy this and paste this in here, just in case this player
does not have any key inside of the time last shot table.
Otherwise what we can go ahead and do is we can check if the current tick minus the time since we last
shot, get the player's name for this particular gun.
We can check if this is less than the property's dot reload duration.
If it is, then that means this player has somehow reloaded or fired this event faster than the reload
duration for their gun, which would indicate that this player is probably exploiting.
So we're going to return and not doing anything.
Otherwise.
Well, we can go ahead and do is we can go ahead and calculate how much ammo we need to reload or put
into our magazine.
So we'll create a variable, we'll call it ammo needed.
And we're going to set it equal to math dot minimum.
We're going to get the properties dot max mag ammo and subtract it by the current ammo in our magazine.
And then the second number we're going to pass is the ammo reserve.
So this way we don't subtract more ammo than what is inside of our ammo reserve.
And then once we do that, we can go ahead and set the attribute.
So gun set attribute ammo in mag.
We'll set that equal to the current ammo in our magazine plus the ammo we need.
And then for.
The ammo reserve.
We can go ahead and set that equal to the ammo reserve minus the ammo.
We need to fill into our magazine and that should be all we need to do for our reload function.
So let's go ahead and test it out.
Playtest our game here.
Hit play.
And then we'll go and shoot our guns.
And actually, let's go ahead and see if automatic for our gun is working.
So if I hold down my mouse button, as you can see, my gun is automatic.
And now I've run out of ammo.
So let me press R and reload my gun.
And hopefully now I should be able to shoot my gun again.
Perfect.
Just like that, we have reloaded the gun, and if we go and look at our character and check out the
attributes for the gun.
As you can see it deducted 20 bullets from my ammo reserve.
Very cool.
Now that we have the fundamental system set up for our guns, we can continue to refine this system
by adding further features like bullet holes, animation, and scripting the UI for our guns.
I'll see you in the next lecture.